`include "../../include/cpu_defines.sv"

module AluFunctionUnit(
	input logic cpu_clk,
	input logic cpu_rst_n,
	input logic flush,
	input logic inst1_valid,
	input logic inst2_valid,
	input logic inst1_branch,
	input logic inst2_branch,
	input logic [5: 0] inst1_rd,
	input logic [5: 0] inst2_rd,
	input logic [31: 0] inst1_src1,
	input logic [31: 0] inst1_src2,
	input logic [31: 0] inst2_src1,
	input logic [31: 0] inst2_src2,
	input logic [5: 0] inst1_rob_index,
	input logic [5: 0] inst2_rob_index,
	input logic [`BRANCH_BUFFER_WIDTH-1: 0] inst1_branch_index,
	input logic [`BRANCH_BUFFER_WIDTH-1: 0] inst2_branch_index,
	input logic [`ALUOP_BUS] inst1_aluop,
	input logic [`ALUOP_BUS] inst2_aluop,
	input logic inst1_wb_receive,
	input logic inst2_wb_receive,
	input logic before_alu1_valid,
	input logic before_alu2_valid,
	input logic [`EXC_CODE_BUS] inst1_exccode,
	input logic [`EXC_CODE_BUS] inst2_exccode,
	output logic inst1_end,
	output logic inst2_end,
	output logic [5: 0] out_inst1_rd,
	output logic [5: 0] out_inst2_rd,
	output logic [5: 0] out_inst1_rob_index,
	output logic [5: 0] out_inst2_rob_index,
	output logic [31: 0] out_inst1_ans,
	output logic [31: 0] out_inst2_ans,
	output logic inst1_acceptable,
	output logic inst2_acceptable,
	output logic [5: 0] inst1_next_rd,
	output logic [5: 0] inst2_next_rd,
	output logic out_inst1_branch,
	output logic out_inst2_branch,
	output logic [31: 0] branch_ans1,
	output logic [31: 0] branch_ans2,
	output logic [`EXC_CODE_BUS] out_exccode1,
	output logic [`EXC_CODE_BUS] out_exccode2,
	output logic [`BRANCH_BUFFER_WIDTH-1: 0] out_branch_index1,
	output logic [`BRANCH_BUFFER_WIDTH-1: 0] out_branch_index2
);
	logic alu_store1, alu_store2;
	logic [31: 0] alu1_ans, alu2_ans, alu1_ans_next, alu2_ans_next;
	logic inst1_using_extra, inst2_using_extra;
	logic [31: 0] alu1_extra, alu2_extra;
	logic [`EXC_CODE_BUS] exe_exe_exccode1, exe_exe_exccode2;

	assign inst1_end = alu_store1;
	assign inst2_end = alu_store2;

	assign inst1_acceptable = !alu_store1 || inst1_wb_receive;
	assign inst2_acceptable = !alu_store2 || inst2_wb_receive;
	assign inst1_next_rd = inst1_rd;
	assign inst2_next_rd = inst2_rd;
	assign out_inst1_ans = alu1_ans_next;
	assign out_inst2_ans = alu2_ans_next;

	always_ff @(posedge cpu_clk)begin
		if(cpu_rst_n == 1'b0 || flush)begin
			alu_store1 <= 1'b0;
			alu_store2 <= 1'b0;	
			out_inst1_rd <= 0;
			out_inst2_rd <= 0;
			out_inst1_rob_index <= 0;
			out_inst2_rob_index <= 0;
			alu1_ans_next <= 0;
			alu2_ans_next <= 0;
			branch_ans1 <= 0;
			branch_ans2 <= 0;
			out_branch_index1 <= 0;
			out_branch_index2 <= 0;
			out_inst1_branch <= 0;
			out_inst2_branch <= 0;
		end
		else begin
			if(before_alu1_valid)begin
				alu_store1 <= 1'b1;
			end
			else if(inst1_wb_receive)begin
				alu_store1 <= 1'b0;
			end
			if(before_alu2_valid)begin
				alu_store2 <= 1'b1;
			end
			else if(inst2_wb_receive)begin
				alu_store2 <= 1'b0;
			end
			// if(inst1_wb_receive)begin
			out_inst1_rd <= inst1_rd;
			out_inst1_rob_index <= inst1_rob_index;
			alu1_ans_next <= alu1_ans;
			branch_ans1 <= inst1_using_extra ? alu1_extra : alu1_ans;
			out_exccode1 <= exe_exe_exccode1;
			out_branch_index1 <= inst1_branch_index;
			out_inst1_branch <= alu_store1 & inst1_branch;
			// end
			// if(inst2_wb_receive)begin
			out_inst2_rd <= inst2_rd;
			out_inst2_rob_index <= inst2_rob_index;
			alu2_ans_next <= alu2_ans;
			branch_ans2 <= inst2_using_extra ? alu2_extra : alu2_ans;
			out_exccode2 <= exe_exe_exccode2;
			out_branch_index2 <= inst2_branch_index;
			out_inst2_branch <= alu_store2 & inst2_branch;
			// end
		end
	end

	Alu alu1(
		.src1(inst1_src1),
		.src2(inst1_src2),
		.aluop(inst1_aluop),
		.id_exe_exccode(inst1_exccode),
		.ans(alu1_ans),
		.exe_exe_exccode(exe_exe_exccode1)
		//.waddr(waddr0),
		//.raddr(raddr0)
	);

	Alu alu2(
		.src1(inst2_src1),
		.src2(inst2_src2),
		.aluop(inst2_aluop),
		.id_exe_exccode(inst2_exccode),
		.ans(alu2_ans),
		.exe_exe_exccode(exe_exe_exccode2)
		//.waddr(waddr1),
		//.raddr(raddr1)
	);

	BranchExtra branch_extra1(
		.src1(inst1_src1),
		.src2(inst1_src2),
		.aluop(inst1_aluop),
		.using_extra(inst1_using_extra),
		.ans(alu1_extra)
	);

	BranchExtra branch_extra2(
		.src1(inst2_src1),
		.src2(inst2_src2),
		.aluop(inst2_aluop),
		.using_extra(inst2_using_extra),
		.ans(alu2_extra)
	);

endmodule

module BranchExtra(
	input logic [31: 0] src1,
	input logic [31: 0] src2,
	input logic [`ALUOP_BUS] aluop,
	output logic using_extra,
	output logic [31: 0] ans
);
	always_comb begin
		if(aluop == `ALUOP_BGEZAL || aluop == `ALUOP_BLTZAL || aluop == `ALUOP_JAL || aluop == `ALUOP_JALR)begin
			using_extra = 1'b1;
		end	
		else begin
			using_extra = 1'b0;
		end
	end
	assign ans = {32{aluop == `ALUOP_BGEZAL}} & (src1[31] == 0) | {32{aluop == `ALUOP_BLTZAL}} & (src1[31] == 1) | {32{aluop == `ALUOP_JALR}} & src1;

endmodule

module Alu(
	input logic [31: 0] src1,
	input logic [31: 0] src2,
	input logic [`ALUOP_BUS] aluop,
	input logic [`EXC_CODE_BUS] id_exe_exccode,
	output logic [31: 0] ans,
	output logic [`EXC_CODE_BUS] exe_exe_exccode
	// output logic [31: 0] wdata
	//output logic [31: 0] waddr,
	//output logic [5: 0] raddr
);
	logic [31: 0] add_ans;
	Adder adder(
		.A(src1),
		.B(src2),
		.ADD(!(aluop == `ALUOP_SUB || aluop == `ALUOP_SUBU)),
		.S(add_ans)
	);

	logic ov;
	always_comb begin
		case(aluop)
		`ALUOP_ADD:begin
			// + + - - - +
			ov = ((src1[31])&&(src2[31])&&(!add_ans[31])) || ((!src1[31])&&(!src2[31])&&(add_ans[31]));
		end
		`ALUOP_ADDI:begin
			ov = ((src1[31])&&(src2[31])&&(!add_ans[31])) || ((!src1[31])&&(!src2[31])&&(add_ans[31]));
		end
		`ALUOP_SUB:begin
			// + - - - + + 
			ov = ((src1[31])&&(!src2[31])&&(!add_ans[31])) || ((!src1[31])&&(src2[31])&&(add_ans[31]));
		end
		default:begin
			ov = 1'b0;
		end
	endcase
	end
	assign exe_exe_exccode = ov ? `EXC_OV :
							(aluop == `ALUOP_TLT  ||
							aluop == `ALUOP_TLTU ||
							aluop == `ALUOP_TGE  ||
							aluop == `ALUOP_TGEU ||
							aluop == `ALUOP_TNE  ||
							aluop == `ALUOP_TEQ) && ans[0] ? `EXC_TR :
							aluop == `ALUOP_MOVN    && src2 == 0 ||
							aluop == `ALUOP_MOVZ    && !(src2 == 0) ? `EXC_FLUSH : id_exe_exccode;
	logic [4: 0] match_location;
	logic match;
	ALU_match alu_match(src2, src1[7: 0], match_location, match);
	logic [4: 0] clo_location, clz_location;
	priority_encoder_32to5 encoder_clo(~src1, clo_location);
	priority_encoder_32to5 encoder_clz(src1, clz_location);

	logic [31: 0] rl_out;
	RLWINM rlwinm(
		.rs(src1),
		.imm5a(src2[14: 10]),
		.imm5b(src2[9: 5]),
		.imm5c(src2[4: 0]),
		.ans(rl_out)
	);

	always_comb begin
		case(aluop)
			`ALUOP_ADD, `ALUOP_ADDI, `ALUOP_ADDU, `ALUOP_ADDIU: begin
				ans = add_ans;
			end
			`ALUOP_SUB, `ALUOP_SUBU: begin
				ans = add_ans;
			end
			`ALUOP_TGE : begin
				if($signed(src1) >= $signed(src2))begin 
					ans = 1;
				end
				else begin
					ans = 0;
				end 
			end
			`ALUOP_TGEU : begin
				ans = src1 >= src2;
			end
			`ALUOP_SLT, `ALUOP_SLTI, `ALUOP_TLT: begin
				if($signed(src1) < $signed(src2))begin 
					ans = 1;
				end
				else begin
					ans = 0;
				end 
			end
			`ALUOP_SLTU, `ALUOP_SLTIU, `ALUOP_TLTU: begin
				ans = src1 < src2 ? 32'b1 : 32'b0;
				// ans = add_ans[31];
			end
			`ALUOP_AND, `ALUOP_ANDI: begin
				ans = src1 & src2;
			end
			`ALUOP_LUI: begin
				ans = src2 & 32'hffff0000;
			end
			`ALUOP_NOR: begin
				ans = ~(src1 | src2);
			end
			`ALUOP_OR, `ALUOP_ORI: begin
				ans = src1 | src2;
			end
			`ALUOP_XOR, `ALUOP_XORI: begin
				ans = src1 ^ src2;
			end
			`ALUOP_SLL: begin
				ans = src2 << src1;
			end
			`ALUOP_SLLV: begin
				ans = src2 << src1[4: 0];
			end
			`ALUOP_SRA: begin
				ans = $signed(src2) >>> src1;
			end
			`ALUOP_SRAV: begin
				ans = $signed(src2) >>> src1[4: 0];
			end
			`ALUOP_SRL: begin
				ans = src2 >> src1;
			end
			`ALUOP_SRLV: begin
				ans = src2 >> src1[4: 0];
			end
			`ALUOP_MTHI, `ALUOP_MTLO, `ALUOP_MFHI, `ALUOP_MFLO, `ALUOP_JR: begin
				ans = src1;
			end
			`ALUOP_BEQ, `ALUOP_TEQ:begin
				ans = src1 == src2;
			end
			`ALUOP_BNE, `ALUOP_TNE: begin
				ans = src1 != src2;
			end
			`ALUOP_BGEZ: begin
				ans = src1[31] == 0;
			end
			`ALUOP_BGTZ: begin
				ans = src1[31] == 0 && src1 != 0;
			end
			`ALUOP_BLEZ: begin
				ans = src1[31] == 1 || src1 == 0;
			end
			`ALUOP_BLTZAL, `ALUOP_BGEZAL, `ALUOP_JAL, `ALUOP_JALR: begin
				ans = src2;
			end
			`ALUOP_BLTZ: begin
				ans = src1[31] == 1;
			end
			`ALUOP_MATCH:begin
				ans = match ? match_location : 32'hffffffff;
			end
			`ALUOP_MOVN:begin
				ans = src1;
			end
			`ALUOP_MOVZ:begin
				ans = src1;
			end
			`ALUOP_CLO:begin
				ans = src1 == 32'hffffffff ? 32'd32 : 5'd31 - clo_location;
			end
			`ALUOP_CLZ:begin
				ans = src1 == 32'h00000000 ? 32'd32 : 5'd31 - clz_location;
			end
			`ALUOP_RLWINM:begin
				ans = rl_out;
			end
			default: begin
				ans = 0;
			end
		endcase
	end
endmodule

module RLWINM(
	input logic [31: 0] rs,
	input logic [4: 0] imm5a,
	input logic [4: 0] imm5b,
	input logic [4: 0] imm5c,
	output logic [31: 0] ans
);
	
	logic [31: 0] mask, mask_before;
	logic [32: 0] imm5c_extend, imm5b_extend, imm5a_extend;
	logic [63: 0] shift_ans;
	logic bigger;
	assign bigger = imm5c_extend >= imm5b_extend;
	assign imm5c_extend = ((1 << (imm5c+1)) - 1);
	assign imm5b_extend = ((1 << (imm5b+1)) - 1);
	assign imm5a_extend = ((1 << (imm5a)) - 1);
	assign mask_before = (imm5c_extend) ^ (imm5b_extend);
	assign mask = bigger ? mask_before : ~mask_before;
	assign shift_ans = rs << imm5a;
	assign ans = (shift_ans[63: 32] | shift_ans[31: 0]) & mask;

endmodule

module ALU_match(
	input logic [31: 0] src2,
	input logic [7: 0] src1,
	output logic [4: 0] match_location,
	output logic match
);
	logic [31: 0] match_decode;
	assign match_decode[0] = src2[7: 0] == src1;
	assign match_decode[1] = src2[8: 1] == src1;
	assign match_decode[2] = src2[9: 2] == src1;
	assign match_decode[3] = src2[10: 3] == src1;
	assign match_decode[4] = src2[11: 4] == src1;
	assign match_decode[5] = src2[12: 5] == src1;
	assign match_decode[6] = src2[13: 6] == src1;
	assign match_decode[7] = src2[14: 7] == src1;
	assign match_decode[8] = src2[15: 8] == src1;
	assign match_decode[9] = src2[16: 9] == src1;
	assign match_decode[10] = src2[17: 10] == src1;
	assign match_decode[11] = src2[18: 11] == src1;
	assign match_decode[12] = src2[19: 12] == src1;
	assign match_decode[13] = src2[20: 13] == src1;
	assign match_decode[14] = src2[21: 14] == src1;
	assign match_decode[15] = src2[22: 15] == src1;
	assign match_decode[16] = src2[23: 16] == src1;
	assign match_decode[17] = src2[24: 17] == src1;
	assign match_decode[18] = src2[25: 18] == src1;
	assign match_decode[19] = src2[26: 19] == src1;
	assign match_decode[20] = src2[27: 20] == src1;
	assign match_decode[21] = src2[28: 21] == src1;
	assign match_decode[22] = src2[29: 22] == src1;
	assign match_decode[23] = src2[30: 23] == src1;
	assign match_decode[24] = src2[31: 24] == src1;
	assign match_decode[31: 25] = 7'h0;
	assign match_location = match_decode[0] ? 5'b0 :
							match_decode[1] ? 5'd1 :
							match_decode[2] ? 5'd2 :
							match_decode[3] ? 5'd3 :
							match_decode[4] ? 5'd4 :
							match_decode[5] ? 5'd5 :
							match_decode[6] ? 5'd6 :
							match_decode[7] ? 5'd7 :
							match_decode[8] ? 5'd8 :
							match_decode[9] ? 5'd9 :
							match_decode[10] ? 5'd10 :
							match_decode[11] ? 5'd11 :
							match_decode[12] ? 5'd12:
							match_decode[13] ? 5'd13 :
							match_decode[14] ? 5'd14 :
							match_decode[15] ? 5'd15 :
							match_decode[16] ? 5'd16 :
							match_decode[17] ? 5'd17 :
							match_decode[18] ? 5'd18 :
							match_decode[19] ? 5'd19 :
							match_decode[20] ? 5'd20 :
							match_decode[21] ? 5'd21 :
							match_decode[22] ? 5'd22 :
							match_decode[23] ? 5'd23 :
							match_decode[24] ? 5'd24 : 5'd0;
	assign match = |match_decode;

endmodule
